﻿
using System;
using System.Collections.Generic;


namespace Boilen {

    /// <summary>
    /// Represents a conditional compilation symbol for a member (i.e. "#if DEBUG").
    /// </summary>
    public struct CompilationSymbol : IEquatable<CompilationSymbol>, IComparable<CompilationSymbol> {

        /// <summary>Empty conditional compilation symbol.</summary>
        public static readonly CompilationSymbol None = new CompilationSymbol( );

        /// <summary>Conditional compilation symbol for Silverlight 3.0.</summary>
        public static CompilationSymbol Silverlight { get { return new CompilationSymbol( GlobalSettings.SilverlightSymbol, false ); } }

        /// <summary>Conditional compilation symbol for non-Silverlight 3.0.</summary>
        public static CompilationSymbol NotSilverlight { get { return new CompilationSymbol( GlobalSettings.SilverlightSymbol, true ); } }


        /// <summary>
        /// The conditional compilation symbol.
        /// </summary>
        public readonly string Symbol;

        /// <summary>
        /// A value indicating whether this is for the else condition of a symbol (i.e. "#if DEBUG ... #else ... #endif").
        /// </summary>
        public readonly bool IsElseCondition;

        /// <summary>
        /// Gets a value indicating whether the member should compiled conditionally.
        /// </summary>
        public bool IsUnconditional { get { return string.IsNullOrEmpty( this.Symbol ); } }

        /// <summary>
        /// Gets the equivalent stand-along compilation symbol, including negation for else conditions.
        /// </summary>
        public string ConditionalSymbol { get { return this.IsElseCondition ? "!" + this.Symbol : this.Symbol ?? ""; } }


        /// <summary>
        /// Initializes a new instance of the <see cref="CompilationSymbol"/> struct with the specified symbol and else-condition value.
        /// </summary>
        public CompilationSymbol( string symbol, bool elseCondition = false ) {
            this.Symbol = symbol;
            this.IsElseCondition = elseCondition && !string.IsNullOrEmpty( symbol );
        }


        /// <summary>
        /// Determines whether the specified <see cref="CompilationSymbol"/> is covered by the current symbol.
        /// </summary>
        /// <param name="target">The <see cref="CompilationSymbol"/> to compare.</param>
        /// <returns><see langword="true"/> if the current symbol has the same or greater scope than the target symbol; otherwise, <see langword="false"/>.</returns>
        public bool Covers( CompilationSymbol target ) {
            return this.IsUnconditional || this.Equals( target );
        }

        /// <summary>
        /// Returns a <see cref="CompilationSymbol"/> that covers both of the specified symbols.
        /// </summary>
        /// <param name="first">The first symbol to cover.</param>
        /// <param name="second">The second symbol to cover.</param>
        /// <returns>A <see cref="CompilationSymbol"/> covering both the <paramref name="first"/> and <paramref name="second"/> symbols.</returns>
        public static CompilationSymbol Combine( CompilationSymbol first, CompilationSymbol second ) {
            if( first.Covers( second ) )
                return first;
            else if( second.Covers( first ) )
                return second;
            else
                return CompilationSymbol.None;
        }

        /// <summary>
        /// Determines whether the specified <see cref="CompilationSymbol"/> is compatible with the current symbol.
        /// </summary>
        /// <param name="target">The <see cref="CompilationSymbol"/> to compare.</param>
        /// <returns><see langword="true"/> if the current symbol covers or is not contradicted by the target symbol; otherwise, <see langword="false"/>.</returns>
        public bool Compatible( CompilationSymbol target ) {
            return this.Covers( target )
                || target.IsUnconditional
                || (target.ConditionalSymbol.Contains( this.Symbol ) && !target.ConditionalSymbol.Contains( "!" + this.Symbol ));
        }

        /// <summary>
        /// Returns the simplest <see cref="CompilationSymbol"/> that covers both symbols.
        /// </summary>
        /// <param name="other">The other symbol to cover.</param>
        /// <returns>A <see cref="CompilationSymbol"/> including both the <paramref name="first"/> and <paramref name="second"/> symbols.</returns>
        public CompilationSymbol Append( CompilationSymbol other ) {
            CompilationSymbol appended;
            if( this.Covers( other ) )
                appended = other;
            else if( other.Covers( this ) )
                appended = this;
            else
                appended = new CompilationSymbol( this.ConditionalSymbol + " && " + other.ConditionalSymbol, false );

            return appended;
        }


        /// <inheritdoc/>
        public override string ToString( ) {
            if( this.IsUnconditional )
                return "";
            else if( this.IsElseCondition )
                return "#else // !" + this.Symbol;
            else
                return "#if " + this.Symbol;
        }

        /// <inheritdoc/>
        public override int GetHashCode( ) {
            return this.Symbol == null ? 0 : this.Symbol.GetHashCode( ) + this.IsElseCondition.GetHashCode( );
        }

        /// <inheritdoc/>
        public override bool Equals( object obj ) {
            if( obj is CompilationSymbol )
                return false;

            return this.Equals( (CompilationSymbol)obj );
        }

        /// <inheritdoc/>
        public bool Equals( CompilationSymbol other ) {
            return this.Symbol == other.Symbol
                && this.IsElseCondition == other.IsElseCondition;
        }

        /// <inheritdoc/>
        public int CompareTo( CompilationSymbol other ) {
            // For the same Symbol, sort !SYMBOL after SYMBOL.
            int compare = Comparer<string>.Default.Compare( this.Symbol, other.Symbol );
            if( compare == 0 ) {
                compare = this.IsElseCondition.CompareTo( other.IsElseCondition );
            }
            // Otherwise, compare ConditionalSymbol, sorting !CONDITION after CONDITION.
            else {
                compare = Comparer<string>.Default.Compare(
                    this.ConditionalSymbol.Replace( "!", "" ),
                    other.ConditionalSymbol.Replace( "!", "" ) );

                if( compare == 0 )
                    compare = Comparer<string>.Default.Compare(
                        this.ConditionalSymbol.Replace( "!", "ZZZ" ),
                        other.ConditionalSymbol.Replace( "!", "ZZZ" ) );
            }

            return compare;
        }

    }

}
